package com.kaibes.core.base.util;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TypeUtils {
    
    public static List<Class<?>> getTypeList(String typeName) {
        List<Class<?>> typeList = new ArrayList<>();
        Pattern p = Pattern.compile("([^<]+)<(.+)>$");
        Matcher m = p.matcher(typeName);
        try {
            if (m.find()) {
                String genericS = m.group(2);
                String[] genericG = genericS.split(",");
                for (String temp : genericG) {
                    typeList.add(Class.forName(temp));
                }
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        return typeList;
    }
    
    public static Type create(String typeName) {
        return new Type() {
            @Override
            public String toString() {
                return typeName;
            }
        };
    }

    public static Class<?> getClass(Type type) {
        Pattern p = Pattern.compile("([^<]+)<(.+)>$");
        Matcher m = p.matcher(type.getTypeName());
        try {
            if (m.find()) {
                String clazzString = m.group(1);
                return Class.forName(clazzString);
            } else {
                return Class.forName(type.getTypeName());
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
    
    public static Map<String, String> getTypeMap(String typeName) {
        Map<String, String> typeMap = new HashMap<>();
        Pattern p = Pattern.compile("([^<]+)<(.+)>$");
        Matcher m = p.matcher(typeName);
        if (m.find()) {
            String clazzString = m.group(1);
            typeMap.put("class", clazzString);
            String genericS = m.group(2);
            String[] genericG = genericS.split(",");
            try {
                Class<?> clazz = Class.forName(clazzString);
                TypeVariable<?>[] typeVariables = clazz.getTypeParameters();
                for (int i = 0; i < typeVariables.length; i++) {
                    typeMap.put(typeVariables[i].getTypeName(), genericG[i].trim());
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        } else {
            typeMap.put("class", typeName);
        }
        return typeMap;
    }
    public static Map<String, String> getTypeMap(Type type) {
        return getTypeMap(type.getTypeName());
    }
    
    public static Type getTrueType(String typeName, Map<String, String> typeMap) {
        Pattern p = Pattern.compile("([^<]+)<(.+)>$");
        Matcher m = p.matcher(typeName);
        if (m.find()) {
            String clazzString = m.group(1);
            String genericS = m.group(2);
            String[] genericG = genericS.split(",");
            StringBuilder sb = new StringBuilder();
            sb.append(clazzString).append("<");
            for (int i = 0; i < genericG.length; i++) {
                sb.append(typeMap.get(genericG[i].trim())).append(",");
            }
            sb.deleteCharAt(sb.length()-1);
            sb.append(">");
            return create(sb.toString());
        } else {
            String typeString = typeMap.get(typeName);
            try {
                if (typeString != null) {
                    return Class.forName(typeString);
                } else {
                    return Class.forName(typeName);
                }
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
    }
    
    public static boolean looksLike(Class<?> source, Type target) {
        Map<String, String> typeMap = getTypeMap(target);
        try {
            Class<?>  targetClazz = Class.forName(typeMap.get("class"));
            Method[] methods = targetClazz.getMethods();
            for (Method method : methods) {
                try {
                    Method temp = source.getMethod(method.getName(), method.getParameterTypes());
                    return temp.getGenericReturnType().getTypeName().equals(method.getGenericReturnType().getTypeName()) ||
                            temp.getGenericReturnType().getTypeName()
                                .equals(getTrueType(method.getGenericReturnType().getTypeName(), typeMap).getTypeName());
                } catch (NoSuchMethodException | SecurityException e) {
                    return false;
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        return false;
    }
    
}
